package junior.util.mocking;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.Map.Entry;

/**
 * 根据OMocking的标注，生成测试样本集。
 * 
 * @author ZhangXiaoye
 *
 */
public class OMockingManager {
	
	private static final Set<Class<?>> SUPPORTED_TYPES = new HashSet<Class<?>>();
	
	static{
		for(Class<?> clazz: new Class[]{
				int.class, Integer.class, String.class,
				long.class, Long.class,
				double.class, Double.class, BigDecimal.class,
				Date.class}){
			SUPPORTED_TYPES.add(clazz);
		}
	}
	
	private final int idStartFrom;
	
	private final boolean strictJoin;
	
	private final Map<String, MockingClass> classes = new HashMap<String, OMockingManager.MockingClass>();
	
	public OMockingManager() {
		this(true);
	}
	
	public OMockingManager(boolean strictJoin){
		this(100000, strictJoin);
	}
	
	public OMockingManager(int idStartFrom, boolean strictJoin){
		this.idStartFrom = idStartFrom;
		this.strictJoin = strictJoin;
	}
	
	/**
	 * 注册一种样本类型
	 * 
	 * @param mockingType 样本类型
	 * @param mockingInstances 生成的样本数量
	 */
	public void registerType(Class<?> mockingType, int mockingInstances){
		for(final Field field: mockingType.getDeclaredFields()){
			final OMocking mocking = field.getAnnotation(OMocking.class);
			if(mocking != null){
				if(! SUPPORTED_TYPES.contains(field.getType())){
					throw new UnsupportedOperationException(String.format("%s.%s: type %s is not supported.", mockingType.getName(), field.getName(), field.getType().getName()));
				}
				MockingClass clazz = classes.get(mockingType.getName());
				if(clazz == null || clazz.n != mockingInstances){
					clazz = new MockingClass(mockingType, mockingInstances);
					classes.put(mockingType.getName(), clazz);
				}
				clazz.fields.put(field.getName(), new ClassField(clazz, field, field.getType(), mocking, idStartFrom));
			}
		}
	}
	
	private void build(ClassField cf){
		final Random rand = new Random(System.currentTimeMillis());
		final SimpleDateFormat dateFormat = new SimpleDateFormat(cf.mocking.dateFormat());
		if((! cf.built) && (! cf.ignore)){
			while(cf.clazz.instances.size() < cf.clazz.n){
				cf.clazz.instances.add(new HashMap<String, Object>());
			}
			if(cf.joinTo != null){
				// join的
				if(! cf.type.equals(cf.joinTo.type)){
					throw new ClassCastException(String.format("join %s.%s cannot convert type:%s to %s.", cf.clazz.clazz.getName(), cf.field.getName(), cf.type.getSimpleName(), cf.joinTo.type.getSimpleName()));
				}
				MockingClass joinClass = classes.get(cf.joinTo.clazz.clazz.getName());
				for(int i = 0; i < cf.clazz.n; i ++){
					if(rand.nextDouble() * 100 < cf.mocking.nullPercentage()){
						cf.clazz.instances.get(i).remove(cf.field.getName());
					}else{
						// join到自己类型时，只能前向join，避免loop。
						int size = cf.clazz.clazz.equals(joinClass.clazz)? i: joinClass.instances.size();
						if(size <= 3 && cf.mocking.nullPercentage() > 0){
							cf.clazz.instances.get(i).remove(cf.field.getName());
						}else{
							if(cf.mocking.joinUnique()){
								// 10次随机尝试后遍历
								Object value = null;
								for(int t = 0; t < 10; t ++){
									Map<String, Object> joinObject = joinClass.instances.isEmpty()? null: joinClass.instances.get(rand.nextInt(size));
									value = joinObject == null? null: joinObject.get(cf.joinTo.field.getName());
									if(value != null && ! cf.uniqueJoins.contains(value)){
										break;
									}else{
										value = null;
									}
								}
								if(value == null){
									// 循环遍历
									for(int j = 0; j < joinClass.instances.size(); j ++){
										Map<String, Object> joinObject = joinClass.instances.get(j);
										value = joinObject.get(cf.joinTo.field.getName());
										if(! cf.uniqueJoins.contains(value)){
											break;
										}else{
											value = null;
										}
									}
									if(value == null){
										throw new UnsupportedOperationException(String.format("can not match unique %s.%s value from %d %s instances.", 
												cf.clazz.clazz.getName(), cf.field.getName(), joinClass.instances.size(), joinClass.clazz.getName()));
									}
								}
								cf.uniqueJoins.add(value);
								cf.clazz.instances.get(i).put(cf.field.getName(), value);
							}else{
								Map<String, Object> joinObject = joinClass.instances.isEmpty()? null: joinClass.instances.get(rand.nextInt(size));
								Object value = joinObject == null? null: joinObject.get(cf.joinTo.field.getName());
								cf.clazz.instances.get(i).put(cf.field.getName(), value);
							}
						}
					}
				}
				cf.built = true;
			}else{
				// 普通类型，直接生成
				final String field = cf.field.getName();
				if(int.class.equals(cf.type) || Integer.class.equals(cf.type) || String.class.equals(cf.type)){
					if(cf.mocking.numberValues().length > 0){
						// 从给定的若干选择中挑选
						for(Map<String, Object> instance: cf.clazz.instances){
							if(rand.nextDouble() * 100 < cf.mocking.nullPercentage()){
								instance.remove(field);
							}else{
								int value = (int) Math.round(cf.mocking.numberValues()[rand.nextInt(cf.mocking.numberValues().length)]);
								if(String.class.equals(cf.type)){
									String prefix = cf.mocking.stringPrefixs().length > 0? cf.mocking.stringPrefixs()[rand.nextInt(cf.mocking.stringPrefixs().length)]: "";
									String variable = cf.mocking.stringFormat().contains("d")? String.format(cf.mocking.stringFormat(), value): value + "";
									instance.put(field, prefix + variable);
								}else{
									instance.put(field, value);
								}
							}
						}
					}else if(cf.mocking.numberRangeTo() > 0){
						// 从范围内取
						for(Map<String, Object> instance: cf.clazz.instances){
							if(rand.nextDouble() * 100 < cf.mocking.nullPercentage()){
								instance.remove(field);
							}else{
								int range = (int) (cf.mocking.numberRangeTo() - cf.mocking.numberRangeFrom());
								int value = rand.nextInt(range) + (int) cf.mocking.numberRangeFrom();
								if(String.class.equals(cf.type)){
									String prefix = cf.mocking.stringPrefixs().length > 0? cf.mocking.stringPrefixs()[rand.nextInt(cf.mocking.stringPrefixs().length)]: "";
									String variable = cf.mocking.stringFormat().contains("d")? String.format(cf.mocking.stringFormat(), value): value + "";
									instance.put(field, prefix + variable);
								}else{
									instance.put(field, value);
								}
							}
						}
					}else{
						// 持续增长
						for(Map<String, Object> instance: cf.clazz.instances){
							if(rand.nextDouble() * 100 < cf.mocking.nullPercentage()){
								instance.remove(field);
							}else{
								int value = (int) cf.lastNumber ++;
								if(String.class.equals(cf.type)){
									String prefix = cf.mocking.stringPrefixs().length > 0? cf.mocking.stringPrefixs()[rand.nextInt(cf.mocking.stringPrefixs().length)]: "";
									String variable = cf.mocking.stringFormat().contains("d")? String.format(cf.mocking.stringFormat(), value): value + "";
									instance.put(field, prefix + variable);
								}else{
									instance.put(field, value);
								}
							}
						}
					}
				}else if(long.class.equals(cf.type) || Long.class.equals(cf.type)){
					if(cf.mocking.numberValues().length > 0){
						// 从给定的若干选择中挑选
						for(Map<String, Object> instance: cf.clazz.instances){
							if(rand.nextDouble() * 100 < cf.mocking.nullPercentage()){
								instance.remove(field);
							}else{
								instance.put(field, Math.round(cf.mocking.numberValues()[rand.nextInt(cf.mocking.numberValues().length)]));
							}
						}
					}else if(cf.mocking.numberRangeTo() > 0){
						// 从范围内取
						for(Map<String, Object> instance: cf.clazz.instances){
							if(rand.nextDouble() * 100 < cf.mocking.nullPercentage()){
								instance.remove(field);
							}else{
								long range = (long) (cf.mocking.numberRangeTo() - cf.mocking.numberRangeFrom());
								long r = (long) (rand.nextDouble() * range);
								instance.put(field, r + (long) cf.mocking.numberRangeFrom());
							}
						}
					}else{
						// 持续增长
						for(Map<String, Object> instance: cf.clazz.instances){
							if(rand.nextDouble() * 100 < cf.mocking.nullPercentage()){
								instance.remove(field);
							}else{
								instance.put(field, (long) cf.lastNumber ++);
							}
						}
					}
				}else if(double.class.equals(cf.type) || Double.class.equals(cf.type) || BigDecimal.class.equals(cf.type)){
					if(cf.mocking.numberValues().length > 0){
						// 从给定的若干选择中挑选
						for(Map<String, Object> instance: cf.clazz.instances){
							if(rand.nextDouble() * 100 < cf.mocking.nullPercentage()){
								instance.remove(field);
							}else{
								double value = cf.mocking.numberValues()[rand.nextInt(cf.mocking.numberValues().length)];
								if(BigDecimal.class.equals(cf.type)){
									instance.put(field, new BigDecimal(value));
								}else{
									instance.put(field, value);
								}
							}
						}
					}else if(cf.mocking.numberRangeTo() > 0){
						// 从范围内取
						for(Map<String, Object> instance: cf.clazz.instances){
							if(rand.nextDouble() * 100 < cf.mocking.nullPercentage()){
								instance.remove(field);
							}else{
								double range = (cf.mocking.numberRangeTo() - cf.mocking.numberRangeFrom());
								double value = (rand.nextDouble() * range) + cf.mocking.numberRangeFrom();
								if(BigDecimal.class.equals(cf.type)){
									instance.put(field, new BigDecimal(value));
								}else{
									instance.put(field, value);
								}
							}
						}
					}else{
						// 持续增长
						for(Map<String, Object> instance: cf.clazz.instances){
							if(rand.nextDouble() * 100 < cf.mocking.nullPercentage()){
								instance.remove(field);
							}else{
								double value = cf.lastNumber ++;
								if(BigDecimal.class.equals(cf.type)){
									instance.put(field, new BigDecimal(value));
								}else{
									instance.put(field, value);
								}
							}
						}
					}
				}else if(Date.class.equals(cf.type)){
					Date dateFrom = null;
					Date dateTo = null;
					try {
						dateFrom = cf.mocking.dateRangeFrom().length() > 0? dateFormat.parse(cf.mocking.dateRangeFrom()): null;
						dateTo = cf.mocking.dateRangeTo().length() > 0? dateFormat.parse(cf.mocking.dateRangeTo()): null;
					} catch (ParseException e) {
						e.printStackTrace();
					}
					if(dateFrom == null){
						dateFrom = new Date();
					}
					if(dateTo == null){
						dateTo = new Date(dateFrom.getTime() + 365 * 24 * 3600l * 1000);
					}
					// 从范围内取
					for(Map<String, Object> instance: cf.clazz.instances){
						if(rand.nextDouble() * 100 < cf.mocking.nullPercentage()){
							instance.remove(field);
						}else{
							long range = dateTo.getTime() - dateFrom.getTime();
							long value = (long)(rand.nextDouble() * range) + dateFrom.getTime();
							instance.put(field, new Date(value));
						}
					}
				}
				cf.built = true;
			}
		}
		
	}
	
	/**
	 * 重建一批指定类型的样本，其他在{@link #build()}中生成的样本保留。
	 * 
	 * @param mockingTypes
	 * @return
	 * @throws Exception
	 */
	public Map<Class<?>, List<Object>> rebuild(Class<?>... mockingTypes) throws Exception{
		// clear
		final Set<String> keys = new HashSet<String>();
		for(Class<?> clazz: mockingTypes){
			MockingClass mc = classes.get(clazz.getName());
			if(mc != null){
				keys.add(mc.clazz.getName());
				mc.instances.clear();
				for(Entry<String, ClassField> e: mc.fields.entrySet()){
					e.getValue().built = false;
				}
			}
		}
		// scan for joins
		for(final String key: keys){
			MockingClass clazz = classes.get(key);
			for(Entry<String, ClassField> entry: clazz.fields.entrySet()){
				ClassField cf = entry.getValue();
				if(! Object.class.equals(cf.mocking.joinClass())){
					MockingClass joinClass = classes.get(cf.mocking.joinClass().getName());
					if(joinClass == null){
						if(strictJoin){
							throw new ClassNotFoundException(cf.mocking.joinClass().getName());
						}else{
							cf.ignore = true;
						}
					}else{
						ClassField joinField = joinClass.fields.get(cf.mocking.joinFieldName());
						if(joinField == null){
							throw new NoSuchMethodException(String.format("field: %s.%s not found", cf.mocking.joinClass().getName(), cf.mocking.joinFieldName()));
						}
						cf.joinTo = joinField;
					}
				}
			}
		}
		// build simple fields
		for(final String key: keys){
			MockingClass clazz = classes.get(key);
			for(Entry<String, ClassField> entry: clazz.fields.entrySet()){
				ClassField cf = entry.getValue();
				if(Object.class.equals(cf.mocking.joinClass())){
					build(cf);
				}
			}
		}
		// build join fields
		for(final String key: keys){
			MockingClass clazz = classes.get(key);
			for(Entry<String, ClassField> entry: clazz.fields.entrySet()){
				ClassField cf = entry.getValue();
				if(! Object.class.equals(cf.mocking.joinClass())){
					build(cf);
				}
			}
		}
		// output
		Map<Class<?>, List<Object>> classInstances = new HashMap<Class<?>, List<Object>>();
		for(final Class<?> mTName: mockingTypes){
			MockingClass clazz = classes.get(mTName.getName());
			List<Object> instances = new ArrayList<Object>(clazz.instances.size());
			for(int i = 0; i < clazz.instances.size(); i ++){
				instances.add(clazz.clazz.newInstance());
			}
			classInstances.put(clazz.clazz, instances);
			for(Entry<String, ClassField> entry: clazz.fields.entrySet()){
				ClassField cf = entry.getValue();
				String setMethodName = "set" + cf.field.getName().substring(0, 1).toUpperCase() + cf.field.getName().substring(1);
				Method setMethod = clazz.clazz.getMethod(setMethodName, cf.type);
				for(int i = 0; i < clazz.instances.size(); i ++){
					Object value = clazz.instances.get(i).get(cf.field.getName());
					setMethod.invoke(instances.get(i), value);
				}
			}
		}
		return classInstances;
	}
	
	/**
	 * 建立所有类型的样本集合
	 * 
	 * @return
	 * @throws Exception
	 */
	public Map<Class<?>, List<Object>> build() throws Exception{
		final List<String> keys = new ArrayList<String>(classes.keySet());
		List<Class<?>> mockingTypes = new ArrayList<Class<?>>();
		for(final String key: keys){
			MockingClass clazz = classes.get(key);
			mockingTypes.add(clazz.clazz);
		}
		return rebuild(mockingTypes.toArray(new Class[]{}));
	}
	
	static class MockingClass{
		
		public final Class<?> clazz;
		
		public final int n;
		
		public final Map<String, ClassField> fields;
		
		public final List<Map<String, Object>> instances;
		
		public MockingClass(Class<?> clazz, int n){
			this.clazz = clazz;
			this.n = n;
			this.fields = new HashMap<String, ClassField>();
			this.instances = new ArrayList<Map<String,Object>>(n);
		}
		
	}
	
	static class ClassField{
		
		public final MockingClass clazz;
		
		public final Field field;
		
		public final Class<?> type;
		
		public final OMocking mocking;
		
		public final Set<Object> uniqueJoins = new HashSet<Object>();
		
		public ClassField joinTo;
		
		public boolean ignore = false;
		
		public double lastNumber = 0;
		
		public boolean built = false;
		
		public ClassField(MockingClass clazz, Field field, Class<?> type, OMocking mocking, double lastNumber){
			this.clazz = clazz;
			this.field = field;
			this.type = type;
			this.mocking = mocking;
			this.lastNumber = lastNumber;
		}
		
	}
	
}
